package com.bumptech.glide;


import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.ImageHeaderParser;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.data.InputStreamRewinder;
import com.bumptech.glide.load.engine.Engine;
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.engine.cache.MemoryCache;
import com.bumptech.glide.load.engine.prefill.BitmapPreFiller;
import com.bumptech.glide.load.engine.prefill.PreFillType;
import com.bumptech.glide.load.engine.prefill.PreFillType.Builder;

import com.bumptech.glide.load.model.*;
import com.bumptech.glide.load.model.stream.HttpGlideUrlLoader;
import com.bumptech.glide.load.model.stream.HttpUriLoader;
import com.bumptech.glide.load.resource.bitmap.*;
import com.bumptech.glide.load.resource.bytes.ByteBufferRewinder;
import com.bumptech.glide.load.resource.drawable.ResourceDrawableDecoder;
import com.bumptech.glide.load.resource.file.FileDecoder;
import com.bumptech.glide.load.resource.transcode.BitmapBytesTranscoder;
import com.bumptech.glide.load.resource.transcode.BitmapDrawableTranscoder;
import com.bumptech.glide.manager.ConnectivityMonitorFactory;
import com.bumptech.glide.manager.RequestManagerRetriever;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.ImageViewTargetFactory;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.util.LogUtil;
import com.bumptech.glide.util.Util;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.utils.net.Uri;
import java.io.File;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.bumptech.glide.load.resource.bitmap.BitmapEncoder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import ohos.app.Context;

/**
 * A singleton to present a simple static interface for building requests with {@link
 * RequestBuilder} and maintaining an {@link Engine}, {@link BitmapPool}, {@link
 * com.bumptech.glide.load.engine.cache.DiskCache} and {@link MemoryCache}.
 */
public class Glide{
  private static final String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
  private static final String TAG = "Glide";

  private static volatile Glide glide;

  private static volatile boolean isInitializing;

  private final Engine engine;
  private final BitmapPool bitmapPool;
  private final MemoryCache memoryCache;
  private final GlideContext glideContext;
  private final Registry registry;
  private final ArrayPool arrayPool;
  private final RequestManagerRetriever requestManagerRetriever;
  private final ConnectivityMonitorFactory connectivityMonitorFactory;

  private final List<RequestManager> managers = new ArrayList<>();

  private final RequestOptionsFactory defaultRequestOptionsFactory;
  private MemoryCategory memoryCategory = MemoryCategory.NORMAL;
  private Context context = null;

  @Nullable
  private BitmapPreFiller bitmapPreFiller;


  private DisplayAttributes getDisplayAttributes(Context context){

     Optional<Display> optional = DisplayManager.getInstance().getDefaultDisplay(context);
     if (!optional.isPresent()) {
         return null;
     }
     return optional.get().getAttributes();
  }

  @NotNull
  // Double checked locking is safe here.
  @SuppressWarnings("GuardedBy")
  public static Glide get(@NotNull Context context) {
    if (glide == null) {
      GeneratedAppGlideModule annotationGeneratedModule =
          getAnnotationGeneratedGlideModulesHarmony(context);

      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context, annotationGeneratedModule);
        }
      }
    }

    return glide;
  }


  private static void checkAndInitializeGlide(
       @NotNull Context context,
       @Nullable GeneratedAppGlideModule generatedAppGlideModule) {
    // In the thread running initGlide(), one or more classes may call Glide.get(context).
    // Without this check, those calls could trigger infinite recursion.
    if (isInitializing) {
      throw new IllegalStateException(
          "You cannot call Glide.get() in registerComponents(),"
              + " use the provided Glide instance instead");
    }
    isInitializing = true;
    initializeGlide(context, generatedAppGlideModule);
    isInitializing = false;
  }

  /**
   * init
   * @deprecated Use {@link #init(Context, GlideBuilder)} to get a singleton compatible with Glide's
   *     generated API.
   *     <p>This method will be removed in a future version of Glide.
   * @param glide
   */
  @VisibleForTesting
  @Deprecated
  public static synchronized void init(Glide glide) {
    if (Glide.glide != null) {
      tearDown();
    }
    Glide.glide = glide;
  }

  @VisibleForTesting
  public static void init( @NotNull GlideBuilder builder) {
    synchronized (Glide.class) {
      if (Glide.glide != null) {
        tearDown();
      }
      //initializeGlide(context, builder, annotationGeneratedModule); // TODO
    }
  }

  @VisibleForTesting
  public static void tearDown() {
    synchronized (Glide.class) {
      if (glide != null) {
        //glide.getContext().getApplicationContext().unregisterComponentCallbacks(glide); //TODO:
        glide.engine.shutdown();
      }
      glide = null;
    }
  }


  private static void initializeGlide(
       Context context,
       GeneratedAppGlideModule generatedAppGlideModule) {
    initializeGlide(context, new GlideBuilder(), generatedAppGlideModule);
  }


  @SuppressWarnings("deprecation")
  private static void initializeGlide(
      @NotNull Context context,
      @Nullable GlideBuilder builder,
       GeneratedAppGlideModule annotationGeneratedModule) {

/*
    List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
    if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {

      manifestModules = new ManifestParser(context).parse();
    }

    if (annotationGeneratedModule != null
        && !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
      Set<Class<?>> excludedModuleClasses = annotationGeneratedModule.getExcludedModuleClasses();
      Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
      while (iterator.hasNext()) {
        com.bumptech.glide.module.GlideModule current = iterator.next();
        if (!excludedModuleClasses.contains(current.getClass())) {
          continue;
        }
        if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
          LogUtil.info(TAG, "AppGlideModule excludes manifest GlideModule: " + current);
        }
        iterator.remove();
      }
    }

    if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
      for (com.bumptech.glide.module.GlideModule glideModule : manifestModules) {
        LogUtil.info(TAG, "Discovered GlideModule from manifest: " + glideModule.getClass());
      }
    }
    */

    RequestManagerRetriever.RequestManagerFactory factory =
        annotationGeneratedModule != null
            ? annotationGeneratedModule.getRequestManagerFactory()
            : null;
    builder.setRequestManagerFactory(factory);
   // for (com.bumptech.glide.module.GlideModule module : manifestModules) {
      //module.applyOptions(null, builder); // TODO:
    //}
   //if (annotationGeneratedModule != null) {
      //annotationGeneratedModule.applyOptions(null, builder);
    //}
    Glide glide = builder.build(context);
    /*
    for (com.bumptech.glide.module.GlideModule module : manifestModules) {
      try {
        module.registerComponents(null, glide, glide.registry); // TODO:
      } catch (AbstractMethodError e) {
        throw new IllegalStateException(
            "Attempting to register a Glide v3 module. If you see this, you or one of your"
                + " dependencies may be including Glide v3 even though you're using Glide v4."
                + " You'll need to find and remove (or update) the offending dependency."
                + " The v3 module name is: "
                + module.getClass().getName(),
            e);
      }
    }
    if (annotationGeneratedModule != null) {
      annotationGeneratedModule.registerComponents(null, glide, glide.registry); // TODO:
    }
     applicationContext.registerComponentCallbacks(glide);  //TODO:
    */
    init(glide);
    // Glide.glide = glide;
  }


  @Nullable
  @SuppressWarnings({"unchecked", "TryWithIdenticalCatches", "PMD.UnusedFormalParameter"})
  private static GeneratedAppGlideModule getAnnotationGeneratedGlideModulesHarmony(
      Context context) {
    GeneratedAppGlideModule result = null; // TODO:

    return result;
  }

  private static void throwIncorrectGlideModule(Exception e) {
    throw new IllegalStateException(
        "GeneratedAppGlideModuleImpl is implemented incorrectly."
            + " If you've manually implemented this class, remove your implementation. The"
            + " Annotation processor will generate a correct implementation.",
        e);
  }

  @SuppressWarnings("PMD.UnusedFormalParameter")
  Glide(
      @NotNull Context context,
      @NotNull Engine engine,
      @NotNull MemoryCache memoryCache,
      @NotNull BitmapPool bitmapPool,
      @NotNull ArrayPool arrayPool,
      @NotNull RequestManagerRetriever requestManagerRetriever,
      @NotNull ConnectivityMonitorFactory connectivityMonitorFactory,
      int logLevel,
      @NotNull RequestOptionsFactory defaultRequestOptionsFactory,
      @NotNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
      @NotNull List<RequestListener<Object>> defaultRequestListeners,
      boolean isLoggingRequestOriginsEnabled,
      boolean isImageDecoderEnabledForBitmaps) {
    this.engine = engine;
    this.bitmapPool = bitmapPool;
    this.arrayPool = arrayPool;
    this.memoryCache = memoryCache;
    this.requestManagerRetriever = requestManagerRetriever;
    this.connectivityMonitorFactory = connectivityMonitorFactory;
    this.defaultRequestOptionsFactory = defaultRequestOptionsFactory;
    this.context = context;

    registry = new Registry();
    registry.register(new DefaultImageHeaderParser());

    // Right now we're only using this parser for HEIF images, which are only supported on OMR1+.
    // If we need this for other file types, we should consider removing this restriction.
    registry.register(new ExifInterfaceImageHeaderParser());


    List<ImageHeaderParser> imageHeaderParsers = registry.getImageHeaderParsers();

    DisplayAttributes displayattributes = getDisplayAttributes(context);

    // TODO(judds): Make ParcelFileDescriptorBitmapDecoder work with ImageDecoder.
    Downsampler downsampler =
          new Downsampler(
              registry.getImageHeaderParsers(), displayattributes, bitmapPool, arrayPool);


    ResourceDecoder<InputStream, PixelMap> streamBitmapDecoder;
    ResourceDecoder<ByteBuffer, PixelMap> byteBufferBitmapDecoder;
      
    streamBitmapDecoder = new StreamBitmapDecoder(downsampler, arrayPool);
    byteBufferBitmapDecoder = new ByteBufferBitmapDecoder(downsampler);

    ResourceDrawableDecoder resourceDrawableDecoder = new ResourceDrawableDecoder(context);

    ResourceLoader.StreamFactory resourceLoaderStreamFactory =
          new ResourceLoader.StreamFactory(context);
    ResourceLoader.UriFactory resourceLoaderUriFactory =
          new ResourceLoader.UriFactory(context);

    BitmapEncoder bitmapEncoder = new BitmapEncoder(arrayPool);

      BitmapBytesTranscoder bitmapBytesTranscoder = new BitmapBytesTranscoder();
    registry
           .append(ByteBuffer.class, new ByteBufferEncoder())
           .append(InputStream.class, new StreamEncoder(arrayPool))
          /* Bitmaps */
          .append(Registry.BUCKET_BITMAP, ByteBuffer.class, PixelMap.class, byteBufferBitmapDecoder)
          .append(Registry.BUCKET_BITMAP, InputStream.class, PixelMap.class, streamBitmapDecoder);
        /* Encoders */
    registry
            .append(PixelMap.class, PixelMap.class, UnitModelLoader.Factory.<PixelMap>getInstance())
            .append(Registry.BUCKET_BITMAP, PixelMap.class, PixelMap.class, new UnitBitmapDecoder())
            .append(PixelMap.class, bitmapEncoder);
    registry
          /* BitmapDrawables */
          .append(
            Registry.BUCKET_BITMAP_DRAWABLE,
            ByteBuffer.class,
            PixelMapElement.class,
            new BitmapDrawableDecoder<>(context, byteBufferBitmapDecoder))
          .append(
              Registry.BUCKET_BITMAP_DRAWABLE,
              InputStream.class,
              PixelMapElement.class,
              new BitmapDrawableDecoder<>(context, streamBitmapDecoder))
            /* Drawables */
            .append(Uri.class, Element.class, resourceDrawableDecoder)
          .register(new InputStreamRewinder.Factory(arrayPool)); // Till here
          
 /* Files */
         registry
        .register(new ByteBufferRewinder.Factory())
        .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
        .append(File.class, InputStream.class, new FileLoader.StreamFactory())
        .append(File.class, File.class, new FileDecoder())  ;

       registry   
          .append(int.class, InputStream.class, resourceLoaderStreamFactory)
          .append(Integer.class, InputStream.class, resourceLoaderStreamFactory)
          .append(Integer.class, Uri.class, resourceLoaderUriFactory)
          .append(String.class, InputStream.class, new StringLoader.StreamFactory())
          .append(Uri.class, InputStream.class, new HttpUriLoader.Factory());
              
         
    registry
          .append(Uri.class, InputStream.class, new UriLoader.StreamFactory(context))       
          .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
                  .append(byte[].class, ByteBuffer.class, new ByteArrayLoader.ByteBufferFactory())
                  .append(byte[].class, InputStream.class, new ByteArrayLoader.StreamFactory())
    /* Transcoders */
        .register(PixelMap.class, PixelMapElement.class, new BitmapDrawableTranscoder(context))
        .register(PixelMap.class, byte[].class, bitmapBytesTranscoder);

    ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
    glideContext =
          new GlideContext(
              context,
              arrayPool,
              registry,
              imageViewTargetFactory,
              defaultRequestOptionsFactory,
              defaultTransitionOptions,
              defaultRequestListeners,
              engine,
              isLoggingRequestOriginsEnabled,
              logLevel);
  }

  /**
   * Returns the {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} used to
   * temporarily store Bitmaps so they can be reused to avoid garbage
   * collections.
   *
   * <p>Note - Using this pool directly can lead to undefined behavior and strange drawing errors.
   * Any Bitmap added to the pool must not be currently in use in any other
   * part of the application. Any Bitmap added to the pool must be removed
   * from the pool before it is added a second time.
   *
   * <p>Note - To make effective use of the pool, any Bitmap removed from
   * the pool must eventually be re-added. Otherwise the pool will eventually empty and will not
   * serve any useful purpose.
   *
   * <p>The primary reason this object is exposed is for use in custom {@link
   * com.bumptech.glide.load.ResourceDecoder}s and {@link com.bumptech.glide.load.Transformation}s.
   * Use outside of these classes is not generally recommended.
   * @return bitmappool
   */
  @NotNull
  public BitmapPool getBitmapPool() {
    return bitmapPool;
  }

  @NotNull
  public ArrayPool getArrayPool() {
    return arrayPool;
  }

  @NotNull
  ConnectivityMonitorFactory getConnectivityMonitorFactory() {
    return connectivityMonitorFactory;
  }

  @NotNull
  GlideContext getGlideContext() {
    return glideContext;
  }

  /**
   * Pre-fills the {@link BitmapPool} using the given sizes.
   *
   * <p>Enough Bitmaps are added to completely fill the pool, so most or all of the Bitmaps
   * currently in the pool will be evicted. Bitmaps are allocated according to the weights of the
   * given sizes, where each size gets (weight / prefillWeightSum) percent of the pool to fill.
   *
   * <p>Note - Pre-filling is done asynchronously using and {@link IdleHandler}. Any currently
   * running pre-fill will be cancelled and replaced by a call to this method.
   *
   * <p>This method should be used with caution, overly aggressive pre-filling is substantially
   * worse than not pre-filling at all. Pre-filling should only be started in onCreate to avoid
   * constantly clearing and re-filling the {@link BitmapPool}. Rotation should be carefully
   * considered as well. It may be worth calling this method only when no saved instance state
   * exists so that pre-filling only happens when the Activity is first created, rather than on
   * every rotation.
   *
   * @param bitmapAttributeBuilders The list of {@link Builder Builders} representing individual
   *     sizes and configurations of {@link Bitmap}s to be pre-filled.
   */
  @SuppressWarnings("unused") // Public API
  public synchronized void preFillBitmapPool(
          @NotNull PreFillType.Builder... bitmapAttributeBuilders) {
    if (bitmapPreFiller == null) {
      DecodeFormat decodeFormat =
          defaultRequestOptionsFactory.build().getOptions().get(Downsampler.DECODE_FORMAT);
      //bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat);  //TODO:openharmony Will add during caching
    }

    //bitmapPreFiller.preFill(bitmapAttributeBuilders);
  }

  /**
   * Clears as much memory as possible.
   */
  public void clearMemory() {
    // Engine asserts this anyway when removing resources, fail faster and consistently
    Util.assertMainThread();
    // memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
    memoryCache.clearMemory();
    bitmapPool.clearMemory();
    arrayPool.clearMemory();
  }

  /**
   * Clears some memory with the exact amount depending on the given level.
   *
   * @param level
   */
  public void trimMemory(int level) {
    // Engine asserts this anyway when removing resources, fail faster and consistently
    Util.assertMainThread();
    // Request managers need to be trimmed before the caches and pools, in order for the latter to
    // have the most benefit.
    synchronized (managers) {
      for (RequestManager manager : managers) {
        manager.onTrimMemory(level);
      }
    }
    // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.
    memoryCache.trimMemory(level);
    bitmapPool.trimMemory(level);
    arrayPool.trimMemory(level);
  }

  /**
   * Clears disk cache.
   *
   * <p>This method should always be called on a background thread, since it is a blocking call.
   */
  // Public API.
  @SuppressWarnings({"unused", "WeakerAccess"})
  public void clearDiskCache() {
    Util.assertBackgroundThread();
    engine.clearDiskCache();
  }

  /** Internal method.
   * @return request manager retirver
   */
  @NotNull
  public RequestManagerRetriever getRequestManagerRetriever() {
    return requestManagerRetriever;
  }

  /**
   * Adjusts Glide's current and maximum memory usage based on the given {@link MemoryCategory}.
   *
   * <p>The default {@link MemoryCategory} is {@link MemoryCategory#NORMAL}. {@link
   * MemoryCategory#HIGH} increases Glide's maximum memory usage by up to 50% and {@link
   * MemoryCategory#LOW} decreases Glide's maximum memory usage by 50%. This method should be used
   * to temporarily increase or decrease memory usage for a single Activity or part of the app. Use
   * {@link GlideBuilder#setMemoryCache(MemoryCache)} to put a permanent memory size if you want to
   * change the default.
   *
   * @param memoryCategory
   * @return the previous MemoryCategory used by Glide.
   */
  @SuppressWarnings("WeakerAccess") // Public API
  @NotNull
  public MemoryCategory setMemoryCategory(@NotNull MemoryCategory memoryCategory) {
    // Engine asserts this anyway when removing resources, fail faster and consistently
    Util.assertMainThread();
    // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687.
    memoryCache.setSizeMultiplier(memoryCategory.getMultiplier());
    bitmapPool.setSizeMultiplier(memoryCategory.getMultiplier());
    MemoryCategory oldCategory = this.memoryCategory;
    this.memoryCategory = memoryCategory;
    return oldCategory;
  }


  @NotNull
  private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    // Context could be null for other reasons (ie the user passes in null), but in practice it will
    // only occur due to errors with the Fragment lifecycle.
    // Class var1 = Glide.class;
    // synchronized(Glide.class) {
    // Object object = context.getHostContext();
    // if (object instanceof Context) {
    return Glide.get(context).getRequestManagerRetriever();
    // }
    // }

  }


  @NotNull
  public static RequestManager with(@NotNull Context context) {
    return getRetriever(context).get(context);
  }

  @NotNull
  public Registry getRegistry() {
    return registry;
  }

  boolean removeFromManagers(@NotNull Target<?> target) {
    synchronized (managers) {
      for (RequestManager requestManager : managers) {
        if (requestManager.untrack(target)) {
          return true;
        }
      }
    }

    return false;
  }

  void registerRequestManager(RequestManager requestManager) {
    synchronized (managers) {
      if (managers.contains(requestManager)) {
        throw new IllegalStateException("Cannot register already registered manager");
      }
      managers.add(requestManager);
    }
  }

  void unregisterRequestManager(RequestManager requestManager) {
    synchronized (managers) {
      if (!managers.contains(requestManager)) {
        throw new IllegalStateException("Cannot unregister not yet registered manager");
      }
      managers.remove(requestManager);
    }
  }

  //@Override
  //public void onTrimMemory(int level) { //Note: openharmony need to provide these APIs
    //trimMemory(level);
  //}

  //@Override
  //public void onConfigurationChanged(Configuration newConfig) {
    // Do nothing.
  //}

  //@Override
  //public void onLowMemory() {
    //clearMemory();
  //}

  /** Creates a new instance of {@link RequestOptions}. */
  public interface RequestOptionsFactory {

    /** Returns a non-null {@link RequestOptions} object.
     * @return request options
     */
    @NotNull
    RequestOptions build();
  }
}
